package io.github.zerobyteword.springfasterstartup;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.scheduling.annotation.EnableAsync;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;

@EnableAsync
public class AsyncInitBeanMethodInvoker implements MethodInterceptor {
    private static final Logger log = LoggerFactory.getLogger(AsyncInitBeanMethodInvoker.class);
    private static final AsyncActionResult OK = new AsyncActionResult(null);
    private final String beanName;
    private final String beanClass;
    private final ThreadPoolExecutor threadPoolExecutor;
    private final AtomicBoolean startFinished = new AtomicBoolean(false);
    private CompletableFuture<AsyncActionResult> methodFuture = CompletableFuture.completedFuture(null);


    public AsyncInitBeanMethodInvoker(Object targetObject, String beanName, ThreadPoolExecutor threadPoolExecutor) {
        this.beanName = beanName;
        this.beanClass = AopUtils.getTargetClass(targetObject).getSimpleName();
        this.threadPoolExecutor = threadPoolExecutor;
    }

    public void taskDone() throws Throwable {
        AsyncActionResult asyncActionResult = methodFuture.join();
        if (asyncActionResult != null && asyncActionResult.getThrowable() != null) {
            throw asyncActionResult.getThrowable();
        }
        startFinished.set(true);
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (startFinished.get()) {
            return invocation.proceed();
        }
        Class<?> returnType = invocation.getMethod().getReturnType();
        if (returnType == Void.class || returnType == void.class) {
            synchronized (this) {
                methodFuture = methodFuture.thenApplyAsync(v -> {
                    try {
                        log.info("beanName=[{}],class=[{}],method=[{}],异步执行中", this.beanName, this.beanClass, invocation.getMethod().getName());
                        invocation.proceed();
                        return OK;
                    } catch (Throwable e) {
                        return new AsyncActionResult(e);
                    }
                }, threadPoolExecutor);
            }
            return null;
        }
        AsyncActionResult asyncActionResult = methodFuture.join();
        if (asyncActionResult != null && asyncActionResult.getThrowable() != null) {
            throw asyncActionResult.getThrowable();
        }
        return invocation.proceed();
    }
}
